/*
 * Copyright (c) 2013-2019 Huawei Technologies Co., Ltd. All rights reserved.
 * Copyright (c) 2020-2021 Huawei Device Co., Ltd. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice, this list of
 *    conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright notice, this list
 *    of conditions and the following disclaimer in the documentation and/or other materials
 *    provided with the distribution.
 *
 * 3. Neither the name of the copyright holder nor the names of its contributors may be used
 *    to endorse or promote products derived from this software without specific prior written
 *    permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include <stdlib.h>
#include <securec.h>
#include "los_config.h"
#include "los_hook.h"
#include "los_sched_pri.h"
#include "los_process_pri.h"
#include "los_memlimit_pri.h"
#include "los_plimits.h"
#include "los_oom.h"
#include "los_list.h"

ProcMemLimiter g_procMemLimiter;
static TaskMemLimitInfo g_taskMemLimitInfo[LOSCFG_BASE_CORE_TSK_LIMIT] = { 0 };
UINT64 g_plimitMemTmp[LOSCFG_BASE_CORE_PROCESS_LIMIT] = { 0 };
VOID MemLimiterProcessResetState(UINT32 processID);

STATIC VOID PlimitsOomKill(UINT32 currProcessId)
{
    INT32 ret;
    ret = OsKill(currProcessId, SIGKILL, OS_USER_KILL_PERMISSION);
    PRINTK("PlimitsOom: Killed process %d. Result = %d.Please adjust the memory.limit limit of the Plimits group\n", currProcessId, ret);
}

STATIC VOID PlimitsOomHandle(ProcMemLimiter *procMemLimiter, LosTaskCB *faultTask)
{
    if (procMemLimiter->kill_oom_disabled) {
        LOS_TaskSuspend(faultTask->taskID);
        return;
    }
    PlimitsOomKill(faultTask->processID);
    return;
}

VOID *OsMemLimiterAlloc(void)
{
    ProcMemLimiter *plimiter = (ProcMemLimiter *)LOS_KernelMalloc(sizeof(ProcMemLimiter));
    if (plimiter == NULL) {
        return NULL;
    }
    memset_s(plimiter, sizeof(ProcMemLimiter), 0, sizeof(ProcMemLimiter));
    return (void *)plimiter;
}

VOID OsMemLimiterCopy(UINT8 *dest, UINT8 *src)
{
    if ((src == NULL) || (dest == NULL)) {
        return;
    }
    ProcMemLimiter *plimiterDest = (ProcMemLimiter *)dest;
    ProcMemLimiter *plimiterSrc = (ProcMemLimiter *)src;
    plimiterDest->limit = plimiterSrc->limit;
    return;
}

STATIC ProcMemLimiter *MemLimiterGetProcMemLimiter(UINT32 *processID)
{
    ProcLimiterSet *procLimiterSet = g_procLimiterSetArray[*processID];
    if ((procLimiterSet == NULL) || (procLimiterSet->procLimiterList[PROCESS_LIMITER_ID_MEM] == NULL) ||
        (procLimiterSet->procLimiterList[PROCESS_LIMITER_ID_MEM]->private == NULL)) {
        return NULL;
    }
    ProcLimiter *procLimiter = procLimiterSet->procLimiterList[PROCESS_LIMITER_ID_MEM];
    if (procLimiter == NULL) {
        return NULL;
    }
    ProcMemLimiter *procMemLimiter = (ProcMemLimiter *)procLimiter->private;
    return procMemLimiter;
}

STATIC BOOL MemLimiterGetTaskProcessIDs(UINT32 *taskID, UINT32 *processID)
{
    LosTaskCB *runTask = NULL;

    runTask = OsCurrTaskGet();
    if (runTask == NULL) {
        return FALSE;
    }
    *taskID = runTask->taskID;
    if (OS_TID_CHECK_INVALID(*taskID)) {
        return FALSE;
    }
    *processID = runTask->processID;
    if (OS_PID_CHECK_INVALID(*processID)) {
        return FALSE;
    }
    return TRUE;
}

STATIC BOOL MemLimiterCheckState(UINT32 *taskID, UINT32 *processID)
{
    if (!g_isInitProcLimiterSet) {
        return FALSE;
    }
    if (!MemLimiterGetTaskProcessIDs(taskID, processID)) {
        return FALSE;
    }
    return TRUE;
}

VOID OsMemLimiterProcessResetState(UINT32 processID)
{
    LosTaskCB *taskCB = NULL;
    UINT32 taskId;
    ProcMemLimiter *procMemLimiter = NULL;

    procMemLimiter = MemLimiterGetProcMemLimiter(&processID);
    for (taskId = 0; taskId < LOSCFG_BASE_CORE_TSK_LIMIT; ++taskId) {
        taskCB = OS_TCB_FROM_TID(taskId);
        if ((taskCB == NULL) || (OS_PID_CHECK_INVALID(taskCB->processID))) {
            continue;
        }
        if (taskCB->processID != processID) {
            continue;
        }
        g_taskMemLimitInfo[taskId].memUsed = 0;
        g_taskMemLimitInfo[taskId].memAlloced = 0;
        g_taskMemLimitInfo[taskId].pageCount = 0;
    }
    g_plimitMemTmp[processID] = 0;
}

BOOL OsMemLimiterCurrTaskUsedIncrease(size_t pageCount)
{
    UINT32 intSave;
    ProcMemLimiter *procMemLimiter = NULL;
    UINT32 taskID, processID;

    PLIMITS_LOCK(intSave);
    if (!MemLimiterCheckState(&taskID, &processID)) {
        PLIMITS_UNLOCK(intSave);
        return FALSE;
    }
    procMemLimiter = MemLimiterGetProcMemLimiter(&processID);
    if (procMemLimiter == NULL) {
        PLIMITS_UNLOCK(intSave);
        return FALSE;
    }
    UINT64 increasedBytes = pageCount * PAGESIZE;
    if ((procMemLimiter->limit > 0) && ((procMemLimiter->usage + increasedBytes) > procMemLimiter->limit)) {
        procMemLimiter->failcnt++;
        PlimitsOomHandle(procMemLimiter, OS_TCB_FROM_TID(taskID));
        PLIMITS_UNLOCK(intSave);
        return TRUE;
    }
    g_taskMemLimitInfo[taskID].memUsed += increasedBytes;
    g_taskMemLimitInfo[taskID].memAlloced = g_taskMemLimitInfo[taskID].memUsed;
    g_taskMemLimitInfo[taskID].pageCount += pageCount;
    procMemLimiter->pageCount += pageCount;
    g_procMemLimiter.pageCount += pageCount;
    procMemLimiter->usage = procMemLimiter->pageCount * PAGESIZE;
    g_procMemLimiter.usage = g_procMemLimiter.pageCount * PAGESIZE;
    if (procMemLimiter->usage > procMemLimiter->peak) {
        procMemLimiter->peak = procMemLimiter->usage;
    }
    g_plimitMemTmp[processID] += increasedBytes;
    PLIMITS_UNLOCK(intSave);
    return FALSE;
}

VOID OsMemLimiterCurrTaskUsedDecrease(size_t pageCount)
{
    UINT32 intSave;
    ProcMemLimiter *procMemLimiter = NULL;
    UINT32 taskID, processID;

    PLIMITS_LOCK(intSave);
    if (!MemLimiterCheckState(&taskID, &processID)) {
        PLIMITS_UNLOCK(intSave);
        return;
    }
    procMemLimiter = MemLimiterGetProcMemLimiter(&processID);
    if (procMemLimiter == NULL) {
        PLIMITS_UNLOCK(intSave);
        return;
    }
    UINT64 decreasedBytes = pageCount * PAGESIZE;
    g_taskMemLimitInfo[taskID].pageCount = (g_taskMemLimitInfo[taskID].pageCount > pageCount) ?
        (g_taskMemLimitInfo[taskID].pageCount - pageCount) : 0;
    g_taskMemLimitInfo[taskID].memUsed = g_taskMemLimitInfo[taskID].pageCount * PAGESIZE;
    g_taskMemLimitInfo[taskID].memFree += decreasedBytes;
    procMemLimiter->pageCount = (procMemLimiter->pageCount > pageCount) ? (procMemLimiter->pageCount - pageCount) : 0;
    g_procMemLimiter.pageCount = (g_procMemLimiter.pageCount > pageCount) ?
        (g_procMemLimiter.pageCount - pageCount) : 0;
    procMemLimiter->usage = procMemLimiter->pageCount * PAGESIZE;
    g_procMemLimiter.usage = g_procMemLimiter.pageCount * PAGESIZE;
    g_plimitMemTmp[processID] =  (g_plimitMemTmp[processID] > decreasedBytes) ?
        (g_plimitMemTmp[processID] - decreasedBytes) : 0;
    PLIMITS_UNLOCK(intSave);
    return;
}

VOID OsMemLimiterInit(VOID)
{
    UINT32 intSave;
    PLIMITS_LOCK(intSave);
    g_procMemLimiter.usage = 0;
    PLIMITS_UNLOCK(intSave);
}

VOID OsMemLimiterMigrate(VOID)
{
    LosTaskCB *taskCB = NULL;
    ProcLimiterSet *procLimiterSet = NULL;
    ProcMemLimiter *procMemLimiter = NULL;

    UINT64 updateTime = OsGetCurrSchedTimeCycle();
    for (UINT32 taskId = 0; taskId < LOSCFG_BASE_CORE_TSK_LIMIT; ++taskId) {
        taskCB = OS_TCB_FROM_TID(taskId);
        if (OS_PID_CHECK_INVALID(taskCB->processID)) {
            continue;
        }
        procLimiterSet = g_procLimiterSetArray[taskCB->processID];
        if (procLimiterSet == NULL) {
            continue;
        }
        if (procLimiterSet->procLimiterList[PROCESS_LIMITER_ID_MEM] == NULL) {
            continue;
        }
        if (procLimiterSet->procLimiterList[PROCESS_LIMITER_ID_MEM]->private == NULL) {
            continue;
        }
        procMemLimiter =  (ProcMemLimiter *)procLimiterSet->procLimiterList[PROCESS_LIMITER_ID_MEM]->private;
        if (updateTime > procMemLimiter->migrateTime) {
            procMemLimiter->usage = 0;
            procMemLimiter->migrateTime = updateTime;
        }
        procMemLimiter->usage += g_taskMemLimitInfo[taskId].memUsed;
        if (procMemLimiter->usage > procMemLimiter->peak) {
            procMemLimiter->peak = procMemLimiter->usage;
        }
    }
    return;
}

INT32 OsMemLimiterShowStat(struct SeqBuf *seqBuf, VOID *data)
{
    (void)LosBufPrintf(seqBuf, "\ntaskid       memUsed        memAlloced      memFree\n");
    for (int i = 0; i < LOSCFG_BASE_CORE_TSK_LIMIT; ++i) {
        if (g_taskMemLimitInfo[i].memUsed > 0) {
            (void)LosBufPrintf(seqBuf, "%2d    ", i);
            (void)LosBufPrintf(seqBuf, "%10ld   ", g_taskMemLimitInfo[i].memUsed);
            (void)LosBufPrintf(seqBuf, "%10ld   ",  g_taskMemLimitInfo[i].memAlloced);
            (void)LosBufPrintf(seqBuf, "%10ld\n", g_taskMemLimitInfo[i].memFree);
        }
    }
    for (int i = 0; i < LOSCFG_BASE_CORE_PROCESS_LIMIT; ++i) {
        if (g_plimitMemTmp[i] != 0) {
            (void)LosBufPrintf(seqBuf, "g_plimitMemTmp[%d] : %llu\n", i, g_plimitMemTmp[i]);
        }
    }
    return LOS_OK;
}

static inline struct ProcDirEntry *GetCurrDirectory(struct ProcDirEntry *dirEntry)
{
    return ((dirEntry == NULL) || S_ISDIR(dirEntry->mode)) ? dirEntry : dirEntry->parent;
}

static inline ProcLimiterSet *GetProcLimiterSetFromDirEntry(struct ProcDirEntry *dirEntry)
{
    struct ProcDirEntry *currDirectory = GetCurrDirectory(dirEntry);
    return (currDirectory == NULL) || (currDirectory->data == NULL) ? NULL : (ProcLimiterSet *)currDirectory->data;
}

ssize_t OsPlimiterOomShow(struct SeqBuf *seqBuf, VOID *procLimiterSet)
{
    if (seqBuf == NULL) {
        (void)LosBufPrintf(seqBuf, "seqBuf is null.\n");
        return -LOS_NOK;
    }
    if (procLimiterSet == NULL) {
        (void)LosBufPrintf(seqBuf, "procLimiterSet is null.\n");
        return -LOS_NOK;
    }
    ProcLimiterSet *currProcLimiterSet = (ProcLimiterSet *)procLimiterSet;
    if (currProcLimiterSet == NULL) {
        (void)LosBufPrintf(seqBuf, "ProcLimiterSet is null.\n");
        return -LOS_NOK;
    }
    if ((currProcLimiterSet->procLimiterList[PROCESS_LIMITER_ID_MEM]) == NULL) {
        (void)LosBufPrintf(seqBuf, "currProcLimiterSet->procLimiterList[PROCESS_LIMITER_ID_MEM] is null\n");
        return -LOS_NOK;
    }
    if ((currProcLimiterSet->procLimiterList[PROCESS_LIMITER_ID_MEM]->private) == NULL) {
        (void)LosBufPrintf(seqBuf, "currProcLimiterSet->procLimiterList[PROCESS_LIMITER_ID_MEM]->private is NULL.\n");
        return -LOS_NOK;
    }
    if (((ProcMemLimiter *)currProcLimiterSet->procLimiterList[PROCESS_LIMITER_ID_MEM]->private) == NULL) {
        (void)LosBufPrintf(seqBuf, "(ProcMemLimiter *)currProcLimiterSet->procLimiterList[PROCESS_LIMITER_ID_MEM]->private is NULL.\n");
        return -LOS_NOK;
    }
    ProcMemLimiter *currProcMemLimiter = (ProcMemLimiter *)currProcLimiterSet->procLimiterList[PROCESS_LIMITER_ID_MEM]->private;
    if (currProcMemLimiter == NULL) {
        return -LOS_NOK;
    }
    (void)LosBufPrintf(seqBuf, "\nkill_oom_disabled %ld\n", currProcMemLimiter->kill_oom_disabled);
    return LOS_OK;
}

ssize_t OsPlimiterOomWrite(struct ProcFile *pf, const char *buf, size_t count, loff_t *ppos)
{
    UINT64 val;
    char *endPtr;
    ProcLimiterSet *procLimiterSet = GetProcLimiterSetFromDirEntry(pf->pPDE);
    if (!ProcLimiterSetAllowWrite(procLimiterSet)) {
        PRINT_ERR("\nroot plimits files are not permitted to add.\n");
        return -LOS_NOK;
    }
    if (procLimiterSet == NULL) {
        PRINTK("this procLimiterSet is NULL.\n");
        return -LOS_NOK;
    }
    ProcMemLimiter *procMemLimite = procLimiterSet->procLimiterList[PROCESS_LIMITER_ID_MEM]->private;
    if (procMemLimite == NULL) {
        PRINTK("this procMemLimite is NULL.\n");
        return -LOS_NOK;
    }
    val = strtoull(buf, &endPtr, 0);
    if ((endPtr == NULL) || (*endPtr != 0)) {
        PRINT_ERR("input value '%s' is not a invalid value.\n", buf);
        return -LOS_NOK;
    }
    if (!((val == 0) || (val == 1))) {
        PRINT_ERR("\noom_kill_disable error[%llu],This value can be set to 1 or 0 only.\n",val);
        return -LOS_NOK;
    }
    procMemLimite->kill_oom_disabled = val;
    return count;
}